#include "xsn.h"
#include <fstream>
#include <streambuf>

namespace xsn
{
	static const std::string xsnWhiteSpaces = " \n\t\r";
	static const std::string xsnSymbols     = "[];\0";

	static size_t xsnWalkQuotedStr(const std::string& src,size_t start=0)
	{
		size_t i=start+1;
		if(src[start] == '\"' && ((start != 0) && (src[start-1]!='\\')) )
		{
			for(i ; i<src.length() ; i++)
			{
				if(src[i] == '\"' && src[i-1] != '\\')
				{
					return i+1;
				}
			}
		}
		return i;
	}

	static size_t xsnWalkBlockComment(const std::string& src,size_t start=0)
	{
		if( (src[start] == '/') && 
			((start+1 < src.length()) && (src[start+1]=='*')) )
		{
			for(size_t i=start+2 ; i<src.length() ; i++)
			{
				if( (src[i] == '*') && ((i+1 < src.length()) && (src[i+1]=='/')))
				{
					return i+2;
				}
			}
		}
		return start;
	}

	static size_t xsnWalkLineComment(const std::string& src,size_t start = 0)
	{
		if( (src[start] == '/') && 
			((start+1 < src.length()) && (src[start+1]=='/')) )
		{
			for(size_t i=start+2 ; i<src.length() ; i++)
			{
				if( (src[i] == '\n') || (src[i] == '\r') )
				{
					return i+1;
				}
			}
		}
		return start;
	}

	static size_t xsnWalkComment(const std::string& src,size_t start=0)
	{
		start = xsnWalkLineComment(src,start);
		return xsnWalkBlockComment(src,start);
	}

	static size_t xsnWalkWhileChar(const std::string& src,
		const std::string& charList,size_t start=0)
	{
		start = xsnWalkComment(src,start);
		for(size_t i=start ; i<src.length() ; i++)
		{
			if(i>start && src[i-1] == '\"')
			{
				i = xsnWalkQuotedStr(src,i-1);
			}
			i = xsnWalkComment(src,i);
			bool found = true;
			for(size_t c=0 ; c<charList.length() ; c++)
			{
				found &= (src[i] != charList[c]);
			}
			if(found)
			{
				return i;
			}
		}
		return std::string::npos;
	}

	static size_t xsnWalkWhileNotChar(const std::string& src,
									  const std::string& charList,
									  size_t start=0)
	{
		start = xsnWalkComment(src,start);
		for(size_t i=start ; i<src.length() ; i++)
		{
			if(i>start && src[i-1] == '\"')
			{
				i = xsnWalkQuotedStr(src,i-1);
			}
			i = xsnWalkComment(src,i);
			for(size_t c=0 ; c<charList.length() ; c++)
			{
				if(src[i] == charList[c])
				{
					return i;
				}
			}
		}
		return std::string::npos;
	}

	static bool walkNextTree( const std::string& src,std::string::size_type& ndx )
	{
		ndx = xsnWalkWhileChar(src,xsnWhiteSpaces,ndx);
		if(ndx == std::string::npos)
		{
			ndx = src.length();
		}
		else if(src[ndx] == '[')
		{
			ndx++;
			return true;
		}
		return false;
	}

	static void walkEndTree( const std::string& src,std::string::size_type& ndx )
	{
		ndx = xsnWalkWhileNotChar(src,"]",ndx);
		if(ndx != std::string::npos)
		{
			ndx = xsnWalkWhileChar(src,xsnWhiteSpaces,ndx+1);
			if(ndx != std::string::npos)
			{
				return;
			}
		}
		ndx = src.length();
	}

	static bool walkNextNode( const std::string& src,std::string::size_type& ndx )
	{
		ndx = xsnWalkWhileChar(src,xsnWhiteSpaces,ndx);
		if(ndx == std::string::npos)
		{
			ndx = src.length();
		}
		else if(src[ndx] == ';')
		{
			ndx++;
			return true;
		}
		return false;
	}

	//==============================================================================

	bool Node::setAttrib( const std::string& src, size_t& offset )
	{
		if(src[offset] == ';' || src[offset] == ']')
		{
			return false;
		}
		size_t startPos = xsnWalkWhileChar(src,xsnWhiteSpaces,offset);
		if(startPos == std::string::npos)
		{
			offset = src.length();
			return false;
		}
		else
		{
			offset = xsnWalkWhileNotChar(src,xsnSymbols + xsnWhiteSpaces,startPos);
			if(offset == std::string::npos)
			{
				offset = src.length();
			}
			this->attrib = src.substr(startPos,offset-startPos);
			if(this->attrib.empty() && src[offset]!='[')
			{
				return false;
			}
		}
		return true;
	}

	bool Node::set( const std::string& src,size_t& offset )
	{
		if(this->setAttrib(src,offset))
		{
			if(walkNextTree(src,offset)) //find '['
			{
				do{
					NodePtr newNode = Node::create();
					newNode->parent = this->shared_from_this();
					if(newNode->set(src,offset))
					{					
						childs.push_back(newNode);
					}		
				}while(walkNextNode(src,offset));																
				walkEndTree(src,offset); //find and step after ']'
			}

			NodePtr nextNode = Node::create();
			nextNode->parent = this->parent;
			nextNode->next = this->next;
			nextNode->prev = this->shared_from_this();
			this->next = nextNode;	
			if(!nextNode->set(src,offset))
			{
				this->next = nextNode->next;			
			}
			return true;
		}
		return false;		
	}

	bool Node::set( const std::string& src )
	{
		size_t offset = 0;
		return this->set(src,offset);
	}

	Node::Node()
	{

	}

	Node::Node( const std::string& src )
	{
		this->set(src);
	}

	Node::Node( const std::string& src,size_t& offset )
	{
		this->set(src,offset);
	}

	void Node::attachChild( NodePtr node )
	{
		node->detach();
		node->parent = this->shared_from_this();
		this->childs.push_back(node);
	}

	void Node::detach()
	{
		if(this->prev)
		{
			this->prev->next = this->next;
		}
		if(this->next)
		{
			this->next->prev = this->prev;
		}
		if(this->parent)
		{			
			NodePtrArray::iterator ch = this->parent->childs.begin();
			for(ch ; ch!=this->parent->childs.end() ; ch++)
			{
				if(*ch == this->shared_from_this())
				{
					if(this->next)
					{
						*ch = this->next;
					}
					else
					{
						this->parent->childs.erase(ch);						
					}
					break;
				}
			}
			this->parent = nullptr;
		}
		this->next = nullptr;
		this->prev = nullptr;
	}

	void Node::attachAttrib( NodePtr node )
	{
		node->detach();
		node->parent = this->parent;
		node->next = this->next;
		node->prev = this->shared_from_this();
		this->next = node;
	}

	std::string Node::get() const
	{
		return this->attrib;
	}

	std::string Node::get( size_t pos ) const
	{
		size_t i=0;		
		ConstNodePtr nextPtr = this->shared_from_this();
		while(i++<pos)
		{
			nextPtr = nextPtr->next;
		}
		if(nextPtr)
		{
			return nextPtr->get();
		}
		return "";
	}

	NodePtr Node::getChild(size_t pos)
	{
		if(pos < this->childs.size())
		{
			return this->childs.at(pos);
		}
		return nullptr;
	}

	ConstNodePtr Node::getChild( size_t pos ) const
	{
		if(pos < this->childs.size())
		{
			return this->childs.at(pos);
		}
		return nullptr;
	}

	ConstNodePtr Node::getAttrib( size_t pos ) const
	{
		size_t i=0;		
		ConstNodePtr nextPtr = this->shared_from_this();
		while(i++<pos)
		{
			nextPtr = nextPtr->next;
		}
		return nextPtr;
	}

	NodePtr Node::getAttrib( size_t pos )
	{
		size_t i=0;		
		NodePtr nextPtr = this->shared_from_this();
		while(i++<pos)
		{
			nextPtr = nextPtr->next;
		}
		return nextPtr;
	}

	NodePtr Node::duplicate() const
	{
		return Node::create(this->attrib);
	}

	NodePtr Node::duplicateTree() const
	{
		NodePtr newNode = Node::create(this->attrib);
		NodePtrArray::const_iterator it = this->childs.begin();
		for(it ; it!=this->childs.end() ; it++)
		{
			newNode->childs.push_back((*it)->duplicateTree());
		}
		return newNode;
	}

	size_t Node::childCount() const
	{
		return this->childs.size();
	}

	size_t Node::attribCount() const
	{
		size_t result = 1;
		NodePtr nextPtr = this->next;
		while(nextPtr)
		{
			result++;
			nextPtr = nextPtr->next;
		}
		return result;
	}

	NodePtr Node::getParent() const
	{
		return this->parent;
	}

	xsn::NodePtr Node::createFromFile( const std::string& filePath )
	{
		std::ifstream input(filePath);
		std::string src((std::istreambuf_iterator<char>(input)),
			             std::istreambuf_iterator<char>());
		return Node::create(src);
	}

	NodePtr Node::create()
	{
		return NodePtr(new Node());
	}

	NodePtr Node::create( const std::string& src )
	{
		NodePtr newNode = Node::create();
		if(newNode->set(src))
		{
			return newNode;
		}
		return nullptr;
	}

	NodePtr Node::create( const std::string& src,size_t& offset )
	{
		NodePtr newNode = Node::create();
		if(newNode->set(src,offset))
		{
			return newNode;
		}
		return nullptr;
	}
}